Customizing an Environment to your needs

Through this tutorial we will see many code snippets and witness their effects through the log of messages. To ease the reading, we will introduce specific blocks, to hold the log for :

They will take the form of :

This is a message from C++

This is a message from Lua

Using this, we will avoid redundant images showing consoles in order to make the tutorial easier to read.
Alright, time to proceed !

The possibilities

An environment, as stated in last tutorial, holds the memory space accessible to a script, when executed. This space can be augmented, either through C++, either through a script.
For instance, writing and executing :

function foo (x) return 2 * x ; end

Will make the function named foo available within the environment, for any later script execution.
However, in this tutorial, we will focus on the possibilities of the C++ API itself. It offers many capabilities :

We will cover all of the aspects one by one, with a focus on the variables and functions in this particular tutorial.
Through it, we will mix C++ code and Lua code for clarity.
We will be starting where we left off from tutorial 01, meaning that we assume a working environment and script have been created.
The environment needs to be augmented before executing the script, and the Lua sources can simply be replaced within the script. Let's go !

Declaring variables from C++

Variables are considered to be the basic types, like integers, floats, strings and such. From a lua perspective, it would correspond to :

t = "Hey" ; t = 6.5 ; t = false ;

In C++, the way to set such a variable would be :

env->setVar("t", "Hey") ;

Which then becomes available from lua. The function setVar is available through different overloads, depending on the variable type to set.
Let's check if the variable is accessible by running :

print(t) ;
Hey

What happened here is that the variable t has been set from C++ to hold the string "Hey". The environment kept that in memory, and next execution of script is accessing this new value right away.
Reciprocally, we can also set a variable from Lua and read it in C++, as an environment's memory is also accessible from the API. For instance, let's run this script after setting the variable from C++ :

t = t .. " you" ;

Now we can try getting it within C++ after the script execution :

std::string t = env->getVar("t", "") ; logger->log(t, "main") ;
Hey you

What we did here is take the content of the variable we just set within C++ ("Hey"), append it to a new string coming from Lua (" you"), and access it from C++ again after execution.
The getVar function also comes with many overloads, depending on the type of variable to retrieve. The second parameter is the default value to return if the variable is not found, but it also allows to choose the right overload to call.
To conclude, the logged result demonstrates well the interoperability offered by an environment, between the scripts and C++.

Linking a function between Lua and C++

As we saw, an environment allows to link a C++ function against an environment. Using those capabilities, it will be possible to call the functions we declare, from Lua itself.
Let's test the functionality, by first including :

#include <NilkinsScripts/Environments/Functions/Function.h>

Now, we can start declaring and setting up a function from scratch :

nkScripts::Function* func = env->setFunc("customCallback") ; func->setFunction(customCallback) ;

Creating a function is very simple : set it on the environment with the setFunc method, and change its parameters. Which is what we are doing here, by requesting it from the environment, and setting the C++ function it should call, using its setFunction method.
Note that a function is managed by the environment it was created in. As such, you should not worry about the memory returned.
We set the callback to a function named customCallback, that we now need to write :

nkScripts::OutputValue customCallback (const nkScripts::DataStack& stack) { return nkScripts::OutputValue(5) ; }

This is the pattern of a callback function within the nkScripts component (called a FunctionCallback).
First, it will have to return an OutputValue, which is a structure that will hold whatever result the function has to return.
It will accept a DataStack, corresponding to an array of arguments, given from Lua.

The function body, for now, simply returns an integer. Let's see how we can address the function from Lua :

t = customCallback() ; print(t) ;
5

From Lua's perspective, this is a function directly callable, like any other. However, it will seek the callback we specified in the environment, and get the value directly from C++.
Let's make it a little more complex and add some parameters to the function declaration. Within nkScripts, the arguments of a function have to be specified, along with their types.
This allows to control and properly flag errors happening during execution. As such, we need to augment our function declaration like this :

func->addParameter(nkScripts::FUNCTION_PARAMETER_TYPE::STRING) ; func->addParameter(nkScripts::FUNCTION_PARAMETER_TYPE::STRING) ;

Now our function knows it has to expect two strings upon a call from Lua. There are many types available, from basic to user defined types.
Parameters will be expected in the order they are declared, so be sure to declare them in the right order when it matters.
After that setup, execution will check that a call to the function has 2 parameters and that those parameters are both strings. If it is not the case, the script execution will return an error.
If the call is correctly made, parameters will be provided within the DataStack provided to the callback. This means we can modify our customCallback body to :

std::string result = stack[0]._valString + " " + stack[1]._valString ; return nkScripts::OutputValue(result) ;

Access to the stack is made by indexing the parameters, that are filled in the order they are passed.
For each entry within the stack, we need to access the right union member. Here it is _valString, which allows to access parameters as strings.
So now our function takes both parameters given from Lua, and concatenates them around a space.
Notice how the OutputValue now takes a string as a return type. Returning a result from a callback is very transparent and goes through the different overload for its constructors.
Let's change a bit our Lua code now :

t = customCallback("nkScripts", "rocks") ; print(t) ;
nkScripts rocks

As simple as that. Lua provides the parameters, and our callback concatenates them before returning the result, now accessible directly within Lua.
If we were to forget a parameter, for instance, our execution would stop with :

[LuaEnvironment] Error while executing script 'firstScript' : Error l.3 while calling customCallback. Expected string, got no value.

Having this security allows to be more robust, spot mistakes more easily, and be able to write the callbacks indexing the stack without worrying about parameters being filled or not.

Conclusion

And this concludes this tutorial about variables and functions. In there we saw that from C++, it is possible to :

We already have quite some controls to work with, but there are still some more aspects of the component we need to introduce. Let's move to the next tutorial !